package com.cocotingo.snail.template.models;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.cocotingo.snail.template.TemplateSyntaxException;
import com.cocotingo.snail.template.models.LayoutFactorNode.FittingLayoutFactorNode;
import com.cocotingo.snail.template.models.LayoutFactorNode.NeighborLayoutFactorNode;
import com.cocotingo.snail.template.models.LayoutFactorNode.ReferencedLayoutFactorNode;

public class TemplateNode extends IDNode {
	
	private VariablesNode variables;
	private ViewNode rootView;
	
	public TemplateNode() {
	}
	
	public ViewNode getRootViewNode() {
		return rootView;
	}
	
	public void setRootViewNode(ViewNode rootView) {
		this.rootView = rootView;
	}
	
	public VariablesNode getVariablesNode() {
		return variables;
	}
	
	public void setVariablesNode(VariablesNode variables) {
		this.variables = variables;
	}
	
	@Override
	public void validateNode() throws TemplateSyntaxException {
		super.validateNode();
		if (rootView == null) throw new TemplateSyntaxException(this, "TemplateNode must have a root ViewNode.");
	}
	
	@Override
	public Iterable<Node> children() {
		return new Iterable<Node>() {
			@Override
			public Iterator<Node> iterator() {
				return new Iterator<Node>() {
					int it = 0;
					@Override
					public boolean hasNext() {
						if (it == 0) {
							if (variables != null) return true;
							else it = 1;
						}
						if (it == 1) {
							if (rootView != null) return true;
							else it = 2;
						}
						return false;
					}
					@Override
					public Node next() {
						if (it == 0) {
							it++;
							return variables;
						}
						if (it == 1) {
							it++;
							return rootView;
						}
						return null;
					}
					@Override
					public void remove() {
						throw new UnsupportedOperationException();
					}
				};
			}
		};
	}
	
	public List<LayoutFactorPair> createSortedLayoutFactorPairs() throws TemplateSyntaxException {
		// generate all pairs
		Map<String, ViewNode> nameMap = new HashMap<String, ViewNode>();
		createViewNodeNameMap(rootView, nameMap);
		Map<ViewNode, LayoutFactorPair> pairs_h = new HashMap<ViewNode, LayoutFactorPair>();
		Map<ViewNode, LayoutFactorPair> pairs_v = new HashMap<ViewNode, LayoutFactorPair>();
		createHorizontalLayoutFactorPairs(rootView, 0, pairs_h);
		createVerticalLayoutFactorPairs(rootView, 0, pairs_v);
		
		// generate dependency
		buildLayoutFactorPairDependency(rootView, null, nameMap, pairs_h);
		buildLayoutFactorPairDependency(rootView, null, nameMap, pairs_v);
		
		// sort pairs
		List<LayoutFactorPair> rst = new ArrayList<LayoutFactorPair>(pairs_h.size() + pairs_v.size());
		sortLayourFactorByDependency(rst, pairs_h);
		sortLayourFactorByDependency(rst, pairs_v);
		
		// check if there is any dead loop in dependency graph
		if (pairs_h.size() > 0) {
			LayoutFactorPair pair = pairs_h.values().iterator().next();
			throw new TemplateSyntaxException(pair.getFactor1(), "Incorrect layout factor dependency on view index: " + pair.getViewIndex() + ".");
		}
		if (pairs_v.size() > 0) {
			LayoutFactorPair pair = pairs_v.values().iterator().next();
			throw new TemplateSyntaxException(pair.getFactor1(), "Incorrect layout factor dependency on view index: " + pair.getViewIndex() + ".");
		}
		return rst;
	}
	
	private void createViewNodeNameMap(ViewNode node, Map<String, ViewNode> map) {
		String id = node.getID();
		if (id != null && !id.isEmpty()) map.put(id, node);
		for (ViewNode subNode : node.viewNodes()) {
			createViewNodeNameMap(subNode, map);
		}
	}
	
	private int createHorizontalLayoutFactorPairs(ViewNode node, int viewIndex, Map<ViewNode, LayoutFactorPair> pairs) throws TemplateSyntaxException {
		LayoutNode layoutNode = node.getLayoutNode();
		if (layoutNode != null) {
			LayoutFactorNode[] factors = layoutNode.getHorizentalFactors();
			if (factors.length == 2) {
				LayoutFactorPair pair = new LayoutFactorPair(viewIndex, factors[0], factors[1]);
				pairs.put(node, pair);
			} else if (factors.length != 0) {
				throw new TemplateSyntaxException(layoutNode, "Incorrect layout factors: " + factors.length + ".");
			}
		}
		for (ViewNode subNode : node.viewNodes()) {
			viewIndex = createHorizontalLayoutFactorPairs(subNode, viewIndex + 1, pairs);
		}
		return viewIndex;
	}
	
	private int createVerticalLayoutFactorPairs(ViewNode node, int viewIndex, Map<ViewNode, LayoutFactorPair> pairs) throws TemplateSyntaxException {
		LayoutNode layoutNode = node.getLayoutNode();
		if (layoutNode != null) {
			LayoutFactorNode[] factors = layoutNode.getVerticalFactors();
			if (factors.length == 2) {
				LayoutFactorPair pair = new LayoutFactorPair(viewIndex, factors[0], factors[1]);
				pairs.put(node, pair);
			} else if (factors.length != 0) {
				throw new TemplateSyntaxException(layoutNode, "Incorrect layout factors: " + factors.length + ".");
			}
		}
		for (ViewNode subNode : node.viewNodes()) {
			viewIndex = createVerticalLayoutFactorPairs(subNode, viewIndex + 1, pairs);
		}
		return viewIndex;
	}
	
	private void buildLayoutFactorPairDependency(ViewNode node, ViewNode superNode, Map<String, ViewNode> nameMap, Map<ViewNode, LayoutFactorPair> pairs) {
		LayoutFactorPair pair = pairs.get(node);
		if (pair != null) {
			LayoutFactorNode factor = pair.getFactor1();
			if (factor.getFactorType() == LayoutFactorNode.RIGHT ||
				factor.getFactorType() == LayoutFactorNode.BOTTOM ||
				factor.getLayoutType() == LayoutFactorNode.LAYOUT_CENTERED) {
				if (pairs.containsKey(superNode)) {
					pair.addDependency(pairs.get(superNode));
				}
			}
			String id = getLayoutFactorDependencyName(factor);
			ViewNode n = nameMap.get(id);
			if (pairs.containsKey(n)) pair.addDependency(pairs.get(n));
			
			factor = pair.getFactor2();
			if (factor.getFactorType() == LayoutFactorNode.RIGHT ||
				factor.getFactorType() == LayoutFactorNode.BOTTOM ||
				factor.getLayoutType() == LayoutFactorNode.LAYOUT_CENTERED) {
				if (pairs.containsKey(superNode)) {
					pair.addDependency(pairs.get(superNode));
				}
			}
			id = getLayoutFactorDependencyName(factor);
			n = nameMap.get(id);
			if (pairs.containsKey(n)) pair.addDependency(pairs.get(n));
		}
		
		for (ViewNode subNode : node.viewNodes()) {
			buildLayoutFactorPairDependency(subNode, node, nameMap, pairs);
		}
	}
	
	private static String getLayoutFactorDependencyName(LayoutFactorNode factor) {
		switch (factor.getLayoutType()) {
		case LayoutFactorNode.LAYOUT_REFERENCED:
			return ((ReferencedLayoutFactorNode) factor).getReferencedView();
		case LayoutFactorNode.LAYOUT_NEIGHBOR:
			return ((NeighborLayoutFactorNode) factor).getNeighborView();
		case LayoutFactorNode.LAYOUT_FITTING:
			return ((FittingLayoutFactorNode) factor).getContentView();
		default:
			return null;
		}
	}
	
	private void sortLayourFactorByDependency(List<LayoutFactorPair> rst, Map<ViewNode, LayoutFactorPair> pairs) {
		MAINLOOP: while (true) {
			// find a factor which has no dependency
			for (ViewNode key : pairs.keySet()) {
				LayoutFactorPair pair = pairs.get(key);
				if (pair.dependencyCount() == 0) {
					// remove dependency
					for (LayoutFactorPair p : pairs.values()) {
						p.removeDependency(pair);
					}
					rst.add(pairs.remove(key));
					continue MAINLOOP;
				}
			}
			return;
		}
	}

	@Override
	protected String getXMLNodeName() {
		return "Template";
	}

	@Override
	protected String getXMLAttributes() {
		return String.format("id=\"%s\"", getID());
	}
	
}
